Skip to content

Support multiple [Facet] attributes on the same target type (multi-source mapping)#334

Merged
Tim-Maes merged 5 commits intomasterfrom
copilot/map-different-source-types
Apr 15, 2026
Merged

Support multiple [Facet] attributes on the same target type (multi-source mapping)#334
Tim-Maes merged 5 commits intomasterfrom
copilot/map-different-source-types

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 14, 2026

[Facet] already had AllowMultiple = true but the generator silently ignored every attribute after the first — ModelBuilder always read context.Attributes[0], and even when the pipeline produced duplicate models they were deduplicated away.

What changed

Generator pipeline

  • ModelBuilder: added BuildModels() that iterates all context.Attributes, one model per [Facet]; BuildModel now delegates to a shared private BuildModelForAttribute.
  • FacetGenerator: switched from .Select(BuildModel).SelectMany(BuildModels); groups models by target FullName before emitting; facetLookup uses first-wins instead of ToDictionary (which would throw on duplicate keys).

Code generation for multi-source targets

  • CodeBuilder.GenerateCombined: emits a single .g.cs with the union of all source members (deduped by name, first-wins), then generates per-source artefacts:
    • Constructors / FromSource overloads — no naming change needed (naturally overloaded by parameter type).
    • ProjectionFrom{SourceSimpleName} static property per source (avoids static-property name collision).
    • To{SourceSimpleName}() instance method per source (can't overload on return type); BackTo alias suppressed for multi-source.
  • Shared artefacts (parameterless ctor, copy ctor, equality) are generated once from the primary model.
  • MemberGenerator.GenerateMembers accepts an optional membersOverride list for the union-member pass.
  • ProjectionGenerator / ToSourceGenerator accept optional name parameters; single-source output is fully backward-compatible (Projection, ToSource(), BackTo() unchanged).

Documentation

  • docs/06_AdvancedScenarios.md: new "Multiple Source Types to One Target" section covering motivation, generated API, union-of-members behaviour, per-source GenerateToSource, and a behaviour reference table.
  • docs/README.md and README.md: updated table of contents and features list to reference multi-source mapping.

CI — Preview NuGet publishing

  • New .github/workflows/preview.yml: triggers on every PR push (opened, synchronize, reopened) targeting main/master/develop.
    • Computes a pre-release version: {base_version}-preview.pr{PR_NUMBER}.{RUN_NUMBER} (e.g. 6.1.4-preview.pr42.7).
    • Builds, packs, and publishes all packages to NuGet.org using the same per-package API key routing as release.yml.
    • Posts (and updates) a single bot comment on the PR with the exact version and dotnet add package install command.
    • concurrency: cancel-in-progress per PR prevents stale publishes when multiple commits land quickly.

Usage

[Facet(typeof(UnitEntity), Include = [nameof(UnitEntity.Name)])]
[Facet(typeof(UnitDto),    Include = [nameof(UnitDto.Name)])]
public partial class UnitDropDownDto;

// Generated:
var a = new UnitDropDownDto(unitEntity);          // constructor overload
var b = new UnitDropDownDto(unitDto);
var c = UnitDropDownDto.FromSource(unitEntity);   // factory overload
var d = UnitDropDownDto.FromSource(unitDto);

// Projection per source (multi-source naming)
IQueryable<UnitDropDownDto> q1 = dbContext.Units.Select(UnitDropDownDto.ProjectionFromUnitEntity);
IQueryable<UnitDropDownDto> q2 = dtos.AsQueryable().Select(UnitDropDownDto.ProjectionFromUnitDto);

Copilot AI and others added 2 commits April 14, 2026 10:11
… target type

Agent-Logs-Url: https://github.com/Tim-Maes/Facet/sessions/b99d33a2-f1df-4f63-8a77-f77eee40a137

Co-authored-by: Tim-Maes <91606949+Tim-Maes@users.noreply.github.com>
…GetSourceSimpleName helper

Agent-Logs-Url: https://github.com/Tim-Maes/Facet/sessions/b99d33a2-f1df-4f63-8a77-f77eee40a137

Co-authored-by: Tim-Maes <91606949+Tim-Maes@users.noreply.github.com>
Copilot AI changed the title [WIP] Add mapping from different source types to same target type Support multiple [Facet] attributes on the same target type (multi-source mapping) Apr 14, 2026
Copilot AI requested a review from Tim-Maes April 14, 2026 10:18
@Tim-Maes
Copy link
Copy Markdown
Owner

@copilot Don't forget to update readme's and docs

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 14, 2026

@copilot Don't forget to update readme's and docs

Done in commit 88c8802:

  • docs/06_AdvancedScenarios.md: Added a new "Multiple Source Types to One Target" section with motivation, full generated API examples, union-of-members explanation, reverse mapping (GenerateToSource), and a behaviour reference table.
  • docs/README.md: Added "Multiple source types to one target (multi-source mapping)" to the Advanced Scenarios table of contents entry.
  • README.md: Added a Multi-source mapping bullet to the Advanced Features section.

Copy link
Copy Markdown
Owner

@Tim-Maes Tim-Maes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now.. LGTM

@Tim-Maes Tim-Maes marked this pull request as ready for review April 14, 2026 11:13
@AllanMichaelsen
Copy link
Copy Markdown

AllanMichaelsen commented Apr 15, 2026

I tried adding this branch to my solution instead of the .nuget packages, but it does not run the generators on build (no Dtos are generated). Since I'm new to the code generators, I'm probably missing something.

@Tim-Maes
Copy link
Copy Markdown
Owner

@copilot Update the workflow so that PR's can be published as preview packages to NuGet =)

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 15, 2026

@copilot Update the workflow so that PR's can be published as preview packages to NuGet =)

Done in commit 00e46dc — new .github/workflows/preview.yml:

  • Triggers on every PR push (opened, synchronize, reopened) targeting main/master/develop
  • Computes a pre-release version: {base}-preview.pr{PR_NUMBER}.{RUN_NUMBER} (e.g. 6.1.4-preview.pr42.7)
  • Builds, packs, and publishes all packages to NuGet.org using the same per-package API key routing as release.yml
  • Posts a bot comment on the PR with the exact version and install command; subsequent pushes update the same comment instead of spamming new ones
  • A concurrency group cancels any in-progress run for the same PR when a newer commit arrives

@kwpbartecautoid
Copy link
Copy Markdown

I have taken a quick once over what Copilot has done and some of the parts I could follow looked sensible. (The tests, for example) There are also many parts that I still don't really have enough understanding.

Copilot AI requested a review from Tim-Maes April 15, 2026 07:46
@AllanMichaelsen
Copy link
Copy Markdown

Can you update the PR so it runs the preview generation?

@github-actions
Copy link
Copy Markdown

📦 Preview packages published

Version: 6.1.4-preview.pr334.1

Install with:

dotnet add package Facet --version 6.1.4-preview.pr334.1

This pre-release is published automatically from this PR and will be overwritten on the next push.

@Tim-Maes Tim-Maes added the enhancement New feature or request label Apr 15, 2026
@AllanMichaelsen
Copy link
Copy Markdown

Armed with the preview package I can confirm that t works as expected and generates constructors as well as projections and ToSource

@Tim-Maes
Copy link
Copy Markdown
Owner

Let's ship it. What a time to be alive, didn't even have to open my editor. Thanks !

@Tim-Maes Tim-Maes merged commit 70569bc into master Apr 15, 2026
3 checks passed
@kwpbartecautoid
Copy link
Copy Markdown

Let's ship it. What a time to be alive, didn't even have to open my editor. Thanks !

I must say, Copilot is getting quite scary with its competence... 😮

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mapping from different source types to same target type (different, not multiple concurrent sources)

4 participants